package server

import (
	"context"
	"fmt"
	tech "gitee.com/min65535/gotechback/api/v1/tech/pb"
	"gitee.com/min65535/gotechback/techback/util/request_head"
	"github.com/go-kratos/kratos/v2/metadata"
	"github.com/go-kratos/kratos/v2/middleware"
	"github.com/go-kratos/kratos/v2/transport"
	"github.com/go-kratos/kratos/v2/transport/http"
	log "github.com/sirupsen/logrus"
	"google.golang.org/protobuf/encoding/protojson"
	"io"
	http2 "net/http"
	"strconv"
	"strings"

	"encoding/json"

	"google.golang.org/protobuf/proto"
)

const __Operator = "__GO__TECH__USER_TOKEN"

func needOperator(url string) bool {
	// allNeeds := []string{"/api/v1/auth/approve_withdrawal", "/api/v1/auth/update_withdrawal_switch"}
	// for _, v := range allNeeds {
	//	if strings.Contains(url, v) {
	//		return true
	//	}
	// }
	// return false

	return true
}

// 获取配置不验证用户id
func notNeedUserIdUrl(url string) bool {
	allNotNeeds := []string{"/api/v1/tech/get_cfg"}
	for _, v := range allNotNeeds {
		if strings.Contains(url, v) {
			return true
		}
	}
	return false
}

type RspHead struct {
	Code int32  `json:"code"` // 处理结果码
	Desc string `json:"desc"` // 处理结果描述文本
}

type Resp struct {
	Rsp *RspHead `json:"rsp"`
}

func setMetaDataNew(md metadata.Metadata, hp *request_head.HeadParam) metadata.Metadata {
	if hp == nil {
		return md
	}
	md.Set("token", hp.Token)
	md.Set("requestid", hp.RequestId)
	md.Set("did", hp.Did)
	md.Set("os", hp.Os)
	md.Set("version", hp.Version)
	md.Set("language", hp.Language)
	md.Set("model", hp.Model)
	md.Set("ip", hp.Ip)
	md.Set("channel", hp.Channel)
	md.Set("mac", hp.Mac)
	md.Set("useragent", hp.UserAgent)
	md.Set("userid", fmt.Sprintf("%d", hp.UserId))
	md.Set("idfa", hp.Idfa)
	md.Set("afid", hp.AfId)
	md.Set("locale", hp.Locale)
	md.Set("timeZoneStr", hp.Timezone)
	md.Set("mcc", hp.Mcc)
	md.Set("keyChainID", hp.KeyChainId)
	md.Set("screen", hp.Screen)
	md.Set("pkg", hp.Pkg)
	return md
}

func readHeadParam(tr transport.Transporter) *request_head.HeadParam {
	var url string
	// url = http2.Path(ctx) // kratos v2.0.0-rc6 以前版本
	if trs, ok := tr.(*http.Transport); ok {
		url = trs.PathTemplate()
	}
	token := tr.RequestHeader().Get("token")
	requestId := tr.RequestHeader().Get("requestid")
	did := tr.RequestHeader().Get("did")
	os := tr.RequestHeader().Get("os")
	version := tr.RequestHeader().Get("version")
	language := tr.RequestHeader().Get("language")
	mdl := tr.RequestHeader().Get("model")
	ip := tr.RequestHeader().Get("ip")
	channel := tr.RequestHeader().Get("channel")
	mac := tr.RequestHeader().Get("mac")
	userAgent := tr.RequestHeader().Get("useragent")
	id := tr.RequestHeader().Get("userid")
	if id == "" {
		id = "0"
	}
	idfa := tr.RequestHeader().Get("idfa")
	appsFlyerId := tr.RequestHeader().Get("afid")
	locale := tr.RequestHeader().Get("locale")
	timeZoneStr := tr.RequestHeader().Get("timeZoneStr")
	mcc := tr.RequestHeader().Get("mcc")
	keyChainID := tr.RequestHeader().Get("keyChainID")
	screen := tr.RequestHeader().Get("screen")
	pkg := tr.RequestHeader().Get("pkg")
	uid, _ := strconv.ParseUint(id, 10, 64)

	hp := &request_head.HeadParam{
		Url:        url,
		UserId:     uid,
		RequestId:  requestId,
		Did:        did,
		Os:         os,
		Version:    version,
		Language:   language,
		Model:      mdl,
		Ip:         ip,
		Channel:    channel,
		Mac:        mac,
		UserAgent:  userAgent,
		Token:      token,
		Idfa:       idfa,
		AfId:       appsFlyerId,
		Locale:     locale,
		Timezone:   timeZoneStr,
		Mcc:        mcc,
		KeyChainId: keyChainID,
		Screen:     screen,
		Pkg:        pkg,
	}
	return hp
}

type AuthSpecial interface {
	Auth(hp *request_head.HeadParam) (uint64, error)
}

// Auth noAuthUrl,不需要验权的路由前缀例如 ===> /api/v1/gotech
func Auth(fc AuthSpecial, noAuthUrl ...string) middleware.Middleware {
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (interface{}, error) {
			md := metadata.New()
			tr, ok := transport.FromServerContext(ctx)
			if !ok {
				return nil, &myError{code: RetCode_RequestReject.int32(), desc: "bad request"}
			}
			for _, k := range tr.RequestHeader().Keys() {
				md.Set(k, tr.RequestHeader().Get(k))
				break
			}
			hp := readHeadParam(tr)
			if hp.Url == "" {
				return nil, &myError{code: RetCode_RequestReject.int32(), desc: "bad request url"}
			}
			switch len(noAuthUrl) {
			case 0: // 为空,则全要验权
			default:
				for _, v := range noAuthUrl {
					if strings.Contains(hp.Url, v) {
						if needOperator(hp.Url) {
							if hp.UserId == 0 && !notNeedUserIdUrl(hp.Url) {
								return nil, &myError{code: RetCode_RequestReject.int32(), desc: "invalid no auth user"}
							}
							md.Set(__Operator, fmt.Sprintf("%d", hp.UserId))
						}
						setMetaDataNew(md, hp)
						ctx = metadata.NewServerContext(ctx, md)
						return handler(ctx, req)
					}
				}
			}
			if hp.Token == "" {
				return nil, &myError{code: RetCode_RequestReject.int32(), desc: "permission denied"}
			}
			user, err := fc.Auth(hp)
			if err != nil {
				return nil, &myError{code: RetCode_TokenInvalid.int32(), desc: err.Error()}
			}
			if needOperator(hp.Url) {
				md.Set(__Operator, fmt.Sprintf("%d", user))
			}
			setMetaDataNew(md, hp)
			ctx = metadata.NewServerContext(ctx, md)
			return handler(ctx, req)
		}
	}
}

var MarshalOptions = protojson.MarshalOptions{
	EmitUnpopulated: true,
	UseProtoNames:   true,
}

type JsonEncoder struct {
}

func (j JsonEncoder) Marshal(v interface{}) ([]byte, error) {
	if m, ok := v.(proto.Message); ok {
		return MarshalOptions.Marshal(m)
	}
	return json.Marshal(v)
}

func (j JsonEncoder) Unmarshal(data []byte, v interface{}) error {
	return json.Unmarshal(data, v)
}

func (j JsonEncoder) Name() string {
	return "json"
}

type ProtoEncoder struct {
}

func (j ProtoEncoder) Marshal(v interface{}) ([]byte, error) {
	if m, ok := v.(proto.Message); ok {
		return proto.Marshal(m)
	}
	return nil, nil
}

func (j ProtoEncoder) Unmarshal(data []byte, v interface{}) error {
	if m, ok := v.(proto.Message); ok {
		return proto.Unmarshal(data, m)
	}
	return nil
}

func (j ProtoEncoder) Name() string {
	return "proto"
}

// MyProtoRequestDecoder 新的解码器
func MyProtoRequestDecoder(r *http.Request, v interface{}) error {
	myCode := ProtoEncoder{}
	data, err := io.ReadAll(r.Body)
	if err != nil {
		return err
	}
	if len(data) == 0 {
		return nil
	}
	if err = myCode.Unmarshal(data, v); err != nil {
		return err
	}
	return nil
}

// MyResponseEncoder encodes the object to the HTTP response. 按照正常下划线格式解析
// 也可以直接在主方法main里面设置    json.MarshalOptions.UseProtoNames = true
func MyResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{}) error {
	myCode := JsonEncoder{}
	data, err := myCode.Marshal(v)
	if err != nil {
		return err
	}
	w.Header().Set("Content-Type", "application/"+myCode.Name()+";charset=UTF-8")
	w.Write(data)
	return nil
}

// MyProtoResponseEncoder encodes the object to the HTTP response.
func MyProtoResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{}) error {
	myCode := ProtoEncoder{}
	data, err := myCode.Marshal(v)
	if err != nil {
		return err
	}
	w.Header().Set("Content-Type", "application/"+myCode.Name()+";charset=UTF-8")
	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(string(data))))
	w.Write(data)
	return nil
}

type myError struct {
	code int32
	desc string
}

func (m *myError) GetCode() int32 {
	return m.code
}

func (m *myError) GetMessage() string {
	return m.desc
}

func (m *myError) Error() string {
	return m.desc
}

type RetCode int32

const (
	RetCode_Success                 RetCode = 0   // 成功
	RetCode_TimeOut                 RetCode = 1   // 请求超时
	RetCode_NeedLogin               RetCode = 2   // 需要登陆
	RetCode_SvrInnerError           RetCode = 3   // 内部异常
	RetCode_AlreadyInProgress       RetCode = 4   // 处理中
	RetCode_OpNotPermitted          RetCode = 5   // 不允许的操作
	RetCode_RequestReject           RetCode = 6   // 请求拒绝
	RetCode_TokenInvalid            RetCode = 9   // 无效的token
	RetCode_CommonError_WrongAction RetCode = 110 // 无效操作
	RetCode_CommonError_RateLimit   RetCode = 138 // 请求过于频繁
)

func (r RetCode) int32() int32 {
	return int32(r)
}

func MyErrorEncoder(w http.ResponseWriter, r *http.Request, errOld error) {
	if errOld != nil {
		log.Warnf("MyErrorEncoder#RemoteAddr: %s", r.RemoteAddr)
		// code := http2.StatusBadRequest
		log.Warnf("MyErrorEncoder#errOld: %v", errOld)
		errRsp := &tech.Rsp{RespHead: &tech.RspHead{Code: 3, Desc: "inner error"}}
		if sc, ok := errOld.(interface {
			GetCode() int32
			GetMessage() string
		}); ok {
			log.Warnf("MyErrorEncoder#GetCode: %d", sc.GetCode())
			errRsp.RespHead.Code = sc.GetCode()
			log.Warnf("MyErrorEncoder#GetMessage: %s", sc.GetMessage())
			errRsp.RespHead.Desc = strings.ReplaceAll(sc.GetMessage(), "rpc error: code = Unknown desc = ", "")
			// code = http2.StatusUnauthorized
		} else {
			errRsp.RespHead.Code = RetCode_SvrInnerError.int32()
			errRsp.RespHead.Desc = strings.ReplaceAll(errOld.Error(), "rpc error: code = Unknown desc = ", "")
		}

		myCode := JsonEncoder{}
		data, err := myCode.Marshal(errRsp)
		if err != nil {
			w.WriteHeader(http2.StatusInternalServerError)
			return
		}
		// w.WriteHeader(code)
		w.Header().Set("Content-Type", "application/"+myCode.Name()+";charset=UTF-8")
		w.Write(data)
	}
}

func MyProtoErrorEncoder(w http.ResponseWriter, r *http.Request, errOld error) {
	if errOld != nil {
		log.Warnf("MyErrorEncoder#RemoteAddr: %s", r.RemoteAddr)
		// code := http2.StatusBadRequest
		log.Warnf("MyErrorEncoder#errOld: %v", errOld)
		errRsp := &tech.Rsp{RespHead: &tech.RspHead{Code: 3, Desc: "inner error"}}
		if sc, ok := errOld.(interface {
			GetCode() int32
			GetMessage() string
		}); ok {
			log.Warnf("MyErrorEncoder#GetCode: %d", sc.GetCode())
			errRsp.RespHead.Code = sc.GetCode()
			log.Warnf("MyErrorEncoder#GetMessage: %s", sc.GetMessage())
			errRsp.RespHead.Desc = strings.ReplaceAll(sc.GetMessage(), "rpc error: code = Unknown desc = ", "")
			// code = http2.StatusUnauthorized
		} else {
			errRsp.RespHead.Code = RetCode_SvrInnerError.int32()
			errRsp.RespHead.Desc = strings.ReplaceAll(errOld.Error(), "rpc error: code = Unknown desc = ", "")
		}

		myCode := ProtoEncoder{}
		data, err := myCode.Marshal(errRsp)
		if err != nil {
			w.WriteHeader(http2.StatusInternalServerError)
			return
		}
		// w.WriteHeader(code)
		w.Header().Set("Content-Type", "application/"+myCode.Name()+";charset=UTF-8")
		w.Write(data)
	}
}
